package cn.iocoder.boot;

import java.util.*;
import java.util.stream.Collectors;

public class UserCFRecommender {

    // 用户-物品评分矩阵
    private Map<Integer, Map<Integer, Double>> userItemMatrix;

    public UserCFRecommender(Map<Integer, Map<Integer, Double>> userItemMatrix) {
        this.userItemMatrix = userItemMatrix;
    }

    // 计算两个用户的余弦相似度
    public static double cosineSimilarity(Map<Integer, Double> user1Ratings, Map<Integer, Double> user2Ratings) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        Set<Integer> commonItems = new HashSet<>(user1Ratings.keySet());
        commonItems.retainAll(user2Ratings.keySet());

        for (int item : commonItems) {
            double r1 = user1Ratings.get(item);
            double r2 = user2Ratings.get(item);
            dotProduct += r1 * r2;
            norm1 += Math.pow(r1, 2);
            norm2 += Math.pow(r2, 2);
        }

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    // 计算目标用户与其他用户的相似度
    public Map<Integer, Double> computeUserSimilarities(int targetUser) {
        Map<Integer, Double> similarities = new HashMap<>();
        Map<Integer, Double> targetRatings = userItemMatrix.get(targetUser);

        for (int user : userItemMatrix.keySet()) {
            if (user == targetUser) continue;

            double similarity = cosineSimilarity(targetRatings, userItemMatrix.get(user));
            similarities.put(user, similarity);
        }

        return similarities;
    }

    // 获取前 K 个最相似的用户
    public List<Integer> getTopKSimilarUsers(int targetUser, int k) {
        Map<Integer, Double> similarities = computeUserSimilarities(targetUser);
        List<Map.Entry<Integer, Double>> similarityEntries = new ArrayList<>(similarities.entrySet());

        similarityEntries.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));

        return similarityEntries.stream()
                .limit(k)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    // 为目标用户预测对目标物品的评分
    public double predictRating(int targetUser, int targetItem, int k) {
        List<Integer> topKUsers = getTopKSimilarUsers(targetUser, k);
        Map<Integer, Double> targetRatings = userItemMatrix.get(targetUser);

        double numerator = 0.0;
        double denominator = 0.0;

        for (int user : topKUsers) {
            Map<Integer, Double> userRatings = userItemMatrix.get(user);
            if (userRatings.containsKey(targetItem)) {
                double similarity = cosineSimilarity(targetRatings, userRatings);
                numerator += similarity * userRatings.get(targetItem);
                denominator += Math.abs(similarity);
            }
        }

        return denominator != 0 ? numerator / denominator : 0;
    }

    // 为目标用户生成推荐物品
    public Set<Integer> generateRecommendations(int targetUser, int k) {
        List<Integer> topKUsers = getTopKSimilarUsers(targetUser, k);
        Set<Integer> recommendedItems = new HashSet<>();

        for (int user : topKUsers) {
            Map<Integer, Double> userRatings = userItemMatrix.get(user);
            for (int item : userRatings.keySet()) {
                if (!userItemMatrix.get(targetUser).containsKey(item)) {
                    recommendedItems.add(item);
                }
            }
        }

        return recommendedItems;
    }

    // 输出推荐理由
    public void explainRecommendations(int targetUser, Set<Integer> recommendedItems, int k) {
        System.out.println("推荐理由");
        System.out.println("------------------------------------");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendedItems);

        List<Integer> topKUsers = getTopKSimilarUsers(targetUser, k);
        System.out.println("与目标用户最相似的 " + k + " 个用户: " + topKUsers);

        for (int user : topKUsers) {
            Map<Integer, Double> userRatings = userItemMatrix.get(user);
            for (int item : recommendedItems) {
                if (userRatings.containsKey(item)) {
                    System.out.println("用户 " + user + " 对物品 " + item + " 的评分为: " + userRatings.get(item));
                }
            }
        }

        System.out.println("------------------------------------");
    }

    public static void main(String[] args) {
        // 示例用户-物品评分矩阵
        Map<Integer, Map<Integer, Double>> userItemMatrix = new HashMap<>();

        // 手动创建用户 1 到用户 12 的评分
        for (int i = 1; i <= 12; i++) {
            Map<Integer, Double> userRatings = new HashMap<>();
            for (int j = 101; j <= 110; j++) { // 假设物品 ID 从 101 到 110
                if (j % 10 <= 5) { // 每个用户只对部分物品进行评分
                    userRatings.put(j, Math.random() * 5); // 随机生成 0 到 5 之间的评分
                }
            }
            userItemMatrix.put(i, userRatings);
        }

        UserCFRecommender recommender = new UserCFRecommender(userItemMatrix);

        // 为目标用户生成推荐物品
        int targetUser = 1;
        int k = 2;
        Set<Integer> recommendations = recommender.generateRecommendations(targetUser, k);

        // 输出推荐结果
        System.out.println("====================================");
        System.out.println("推荐结果");
        System.out.println("====================================");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendations);
        System.out.println("------------------------------------");

        // 输出推荐理由
        recommender.explainRecommendations(targetUser, recommendations, k);

        // 预测评分
        int targetItem = 104;
        double predictedRating = recommender.predictRating(targetUser, targetItem, k);

        // 输出预测评分结果
        System.out.println("预测评分结果");
        System.out.println("------------------------------------");
        System.out.println("目标用户: " + targetUser);
        System.out.println("目标物品: " + targetItem);
        System.out.println("预测评分: " + predictedRating);
        System.out.println("====================================");
    }
}